package org.znxs.znxsojsandbox.docker;


import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.util.ArrayUtil;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.*;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import com.github.dockerjava.transport.DockerHttpClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.znxs.znxsojsandbox.model.ExecuteCodeRequest;
import org.znxs.znxsojsandbox.model.ExecuteMessage;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

@Service
@Slf4j
public class DockerJavaRun {


    private static final String DOCKER_HOST = "npipe:////./pipe/dockerDesktopLinuxEngine";
    private static final String IMAGE_JDK = "openjdk:8-alpine";
    private static final Long TIME_OUT = 10000L;

    /**
     * 运行java代码
     */
    public List<ExecuteMessage> dockerRun(ExecuteCodeRequest executeCodeRequest, File userCodeFile) {

        // 创建docker  实例
        DockerHttpClient dockerHttpClient = new ApacheDockerHttpClient.Builder().dockerHost(URI.create(DOCKER_HOST)).maxConnections(100).connectionTimeout(Duration.ofSeconds(30)).responseTimeout(Duration.ofSeconds(45)).build();
        DockerClient dockerClient = DockerClientBuilder.getInstance().withDockerHttpClient(dockerHttpClient).build();

        // 使用dockerClient进行操作
        // ping 操作
        PingCmd pingCmd = dockerClient.pingCmd();
        pingCmd.exec();
        log.info("docker ping 执行成功");

        // 1. 下载镜像
        final boolean[] hasImage = {false};
        // 判断是否有该镜像
        ListImagesCmd listImagesCmd = dockerClient.listImagesCmd();
        List<Image> images = listImagesCmd.exec();
        images.forEach(image -> {
            for (String repoTag : image.getRepoTags()) {
                if (repoTag.equals(IMAGE_JDK)) {
                    hasImage[0] = true;
                    log.info("镜像：" + repoTag + "已经存在");
                    break;
                }
            }
        });
        // 没有镜像就下载镜像
        if (!hasImage[0]) {
            PullImageCmd pullImageCmd = dockerClient.pullImageCmd(IMAGE_JDK);
            // 回调函数
            PullImageResultCallback pullImageResultCallback = new PullImageResultCallback() {
                @Override
                public void onNext(PullResponseItem item) {
                    log.info("下载镜像进行中：" + item.getStatus());
                    super.onNext(item);
                }

                @Override
                public void onComplete() {
                    log.info("下载镜像完成");
                    super.onComplete();
                }

                @Override
                public void onError(Throwable throwable) {
                    log.error("下载镜像异常", throwable);
                    super.onError(throwable);
                }
            };
            try {
                log.info("开始下载镜像" + IMAGE_JDK);
                pullImageCmd.exec(pullImageResultCallback).awaitCompletion();
            } catch (InterruptedException e) {
                log.error("等待下载完成时发生中断", e);
                throw new RuntimeException(e);
            }
        }

        // 2、创建容器并运行
        ListContainersCmd listContainersCmd = dockerClient.listContainersCmd();
        final String[] containerId = {""};
        // 判断容器是否存在
        final boolean[] hasContainer = {false};
        List<Container> ContainerList = listContainersCmd.withShowAll(true).exec();
        ContainerList.forEach(container -> {
            if (IMAGE_JDK.equals(container.getImage())) {
                // 存在容器，不用创建容器
                log.info("存在容器：" + Arrays.toString(container.getNames()) + "，不用创建容器");
                containerId[0] = container.getId();
                hasContainer[0] = true;
            }
        });
        // 不存在容器
        if (!hasContainer[0]) {
            // 判断 容器是否成功
            CreateContainerCmd createContainerCmd = dockerClient.createContainerCmd(IMAGE_JDK);
            // 创建主机的配置选项
            HostConfig hostConfig = new HostConfig();
            // 内存限制
            hostConfig.withMemory(100 * 1000 * 1000L);
            // 限制不让信息从内存写到硬盘
            hostConfig.withMemorySwap(0L);
            // 设置系统根目录设置为只读
            hostConfig.withReadonlyRootfs(false);
            // 限制CPU最多使用1核
            hostConfig.withCpuCount(1L);
//        hostConfig.withSecurityOpts(Arrays.asList("seccomp=安全管理配置字符串"));
            // 映射路径  把本地路径映射到虚拟机路径
            String dockerMatchPath = userCodeFile.getParentFile().getParentFile().getAbsolutePath();
            hostConfig.setBinds(new Bind(dockerMatchPath, new Volume("/app")));
            CreateContainerResponse createContainerResponse = createContainerCmd.withHostConfig(hostConfig)
                    // 禁用了容器的网络连接
                    .withNetworkDisabled(true)
                    // 使容器的根文件系统以只读方式挂载，增加安全性
                    .withReadonlyRootfs(true)
                    // 容器
                    .withAttachStdin(true).withAttachStderr(true).withAttachStdout(true).withTty(true).exec();
            containerId[0] = createContainerResponse.getId();
            log.info("容器创建成功：" + containerId[0]);
        }

        // 3.启动容器
        // 判断容器是否启动
        final boolean[] isRunningContainer = {false};
        listContainersCmd = dockerClient.listContainersCmd();
        List<Container> containerList = listContainersCmd.exec();
        containerList.forEach(container -> {
            if (IMAGE_JDK.equals(container.getImage())) {
                // 存在容器，不用创建容器
                log.info("存在运行容器：" + Arrays.toString(container.getNames()) + "，无需运行容器");
                isRunningContainer[0] = true;
            }
        });

        if (!isRunningContainer[0]) {
            // 执行运行容器
            dockerClient.startContainerCmd(containerId[0]).exec();
            log.info("运行成功");
        }

        // 4.运行代码
        List<String> inputList = executeCodeRequest.getInputList();
        ArrayList<ExecuteMessage> executeMessageList = new ArrayList<>();

        // 获取输入用例 进行遍历
        for (String inputArgs : inputList) {
            // 计算时间
            StopWatch stopWatch = new StopWatch();
            // 创建命令
            ExecCreateCmd execCreateCmd = dockerClient.execCreateCmd(containerId[0]);
            String[] inputArgsArray = inputArgs.split(" ");
            // 执行java命令 java -Xmx256m -Dfile.encoding=UTF-8 -cp %s Main %s
            String[] cmdArray = ArrayUtil.append(new String[]{"java", "-Xmx256m", "-Dfile.encoding=UTF-8", "-cp", "/app/" + userCodeFile.getParentFile().getName(), "Main"}, inputArgsArray);
            // 配置并创建一个新的命令实例，可以使用返回值的id进行【启动】或者【监控】
            ExecCreateCmdResponse execCreateCmdResponse = execCreateCmd.withCmd(cmdArray).withAttachStderr(true).withAttachStdin(true).withAttachStdout(true).exec();
            String execId = execCreateCmdResponse.getId();
            log.info("创建执行命令：" + execId);

            ExecuteMessage executeMessage = new ExecuteMessage();
            final String[] message = {null};
            ArrayList<String> errorMessage = new ArrayList<>();
            long time = 0L;


            // 执行运行命令回调函数
            ExecStartResultCallback execStartResultCallback = new ExecStartResultCallback() {
                @Override
                public void onComplete() {
                    // 如果执行完成，则表示没超时
                    log.info("执行完成");
                    super.onComplete();
                }

                @Override
                public void onNext(Frame frame) {
                    StreamType streamType = frame.getStreamType();
                    if (StreamType.STDERR.equals(streamType)) {
                        errorMessage.add(new String(frame.getPayload()));
                        System.out.println("输出错误结果：" + errorMessage.get(errorMessage.size() - 1));
                    } else {
                        message[0] = new String(frame.getPayload());
                        System.out.println("输出结果：" + message[0]);
                    }
                    super.onNext(frame);
                }

                @Override
                public void onError(Throwable throwable) {
                    log.error("执行过程中出现错误: ", throwable);
                    super.onError(throwable);
                }
            };

            // todo 这个内存监控存在问题，多个程序运行内存消耗相同
            // 新线程监控java命令执行消耗内存
            final long[] maxMemory = {0L};
            final AtomicBoolean shouldContinue = new AtomicBoolean(true);
            // 获取占用内存信息
            StatsCmd statsCmd = dockerClient.statsCmd(containerId[0]);
            ResultCallback<Statistics> statsCmdCallback = new ResultCallback<Statistics>() {
                @Override
                public void onComplete() {

                }

                @Override
                public void onStart(Closeable closeable) {

                }

                @Override
                public void onNext(Statistics statistics) {
                    if (shouldContinue.get()) {
                        System.out.println("内存占用" + statistics.getMemoryStats().getUsage());
                        maxMemory[0] = Math.max(statistics.getMemoryStats().getUsage(), maxMemory[0]);
                    }

                }

                @Override
                public void onError(Throwable throwable) {

                }


                @Override
                public void close() throws IOException {
                    shouldContinue.set(false); // 确保不会再处理新的统计数据
                }
            };


            try {
                // 统计时间
                stopWatch.start();
                // 开始监控
                statsCmd.exec(statsCmdCallback);
                // 执行运行命令
                dockerClient.execStartCmd(execId) // 执行命令【java运行命令】
                        .exec(execStartResultCallback)  // 执行回调函数
                        .awaitCompletion(TIME_OUT, TimeUnit.MILLISECONDS);  // 等待程序执行结束
                stopWatch.stop();
                time = stopWatch.getLastTaskTimeMillis();
                // 停止监控
                log.info("停止监听内存");
                shouldContinue.set(false);
                statsCmd.close();
            } catch (InterruptedException e) {
                log.error("执行被中断");
                throw new RuntimeException(e);
            }
            System.out.println(maxMemory[0]);
            executeMessage.setMessage(message[0]);
            // 处理错误信息
            executeMessage.setErrorMessage(CollectionUtil.join(errorMessage, "\n"));
            executeMessage.setTime(time);
            executeMessage.setMemory(1555L);
            System.out.println(executeMessage.getMemory());
            executeMessageList.add(executeMessage);
        }
        return executeMessageList;
    }


}
